package org.ms.mtx.core.context;

import lombok.extern.slf4j.Slf4j;
import org.ms.mtx.core.annotation.Compensable;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * The Transaction Context Holder
 *
 * @author lry
 */
@Slf4j
public final class TransactionContextHolder {

    private static ThreadLocal<TransactionContext> mtxContextThreadLocal = new ThreadLocal<>();

    /**
     * The build context
     *
     * @param txId        transaction id
     * @param compensable {@link Compensable}
     */
    public static void build(String txId, Compensable compensable) {
        TransactionContext context = new TransactionContext();
        context.setTxId(txId);
        context.setAsync(compensable.async());
        context.setRetriedTimes(0);
        context.setCreateTime(System.currentTimeMillis());
        context.setLastUpdateTime(System.currentTimeMillis());
        // TODO:是否需要从ThreadLocal获取交易ID进行填充?
        context.setId(UUID.randomUUID().toString().replace("-", ""));

        mtxContextThreadLocal.set(context);
    }

    /**
     * The get context
     *
     * @return {@link TransactionContext}
     */
    public static TransactionContext get() {
        return mtxContextThreadLocal.get();
    }

    /**
     * The remove context
     */
    public static void remove() {
        mtxContextThreadLocal.remove();
    }

    /**
     * The transaction cancel
     */
    public static void cancel() {
        cancel(get().getCancels());
    }

    /**
     * The transaction cancel
     *
     * @param cancels {@link Map<String,MethodInvokeContext>}
     */
    public static void cancel(Map<String, MethodInvokeContext> cancels) {
        if (cancels.isEmpty()) {
            return;
        }

        List<MethodInvokeContext> methodInvokeContexts = new ArrayList<>(cancels.values());

        // the reverse rollback
        for (int i = 0; i < methodInvokeContexts.size(); i++) {
            MethodInvokeContext context = methodInvokeContexts.get(methodInvokeContexts.size() - 1 - i);
            try {
                log.info("The starting execute cancel: [{}]", context.toString());
                doCancel(context);
                log.info("The execute cancel is completed: [{}]", context.toString());
            } catch (Throwable t) {
                log.error("The execute cancel is exception: [" + context.toString() + "]", t);
            }
        }
    }

    /**
     * The add cancel event
     *
     * @param key     sub transaction key
     * @param context {@link MethodInvokeContext}
     */
    public static void addCancel(String key, MethodInvokeContext context) {
        mtxContextThreadLocal.get().getCancels().put(key, context);
    }

    /**
     * The get attribute from the current thread
     *
     * @param key attribute key
     * @param <T> class type
     * @return attribute value
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAttribute(String key) {
        return (T) mtxContextThreadLocal.get().getAttributes().get(key);
    }

    /**
     * The add attribute value by key
     *
     * @param key   attribute key
     * @param value attribute value
     */
    public static void addAttribute(String key, Object value) {
        mtxContextThreadLocal.get().getAttributes().put(key, value);
    }

    /**
     * The execute cancel
     *
     * @param methodInvokeContext {@link MethodInvokeContext}
     * @throws Throwable exception
     */
    private static void doCancel(MethodInvokeContext methodInvokeContext) throws Throwable {
        Object beanObject = methodInvokeContext.getBeanObject();
        Method confirmMethod = beanObject.getClass().getDeclaredMethod(
                methodInvokeContext.getMethodName(), methodInvokeContext.getParameterTypes());
        confirmMethod.setAccessible(true);
        confirmMethod.invoke(beanObject, methodInvokeContext.getParameters());
    }

}
